package com.infoauto.opc.gateway.opc.client;

import com.infoauto.opc.gateway.opc.factory.DeviceLogFactory;
import com.infoauto.opc.gateway.opc.factory.SubscribeNodeFactory;
import com.infoauto.opc.gateway.opc.model.INodeType;
import com.infoauto.opc.gateway.opc.model.SubscribeNode;
import com.infoauto.opc.gateway.service.ShiftService;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.sdk.client.api.nodes.VariableNode;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;

import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

public class OpcConnection implements OpcClient {

    private String opcUrl;

    public void setEndpointUrl(String url) {
        this.opcUrl = url;
    }

    @Autowired
    private ShiftService service;

    @Override
    public String getEndpointUrl() {
        return this.opcUrl;
    }

    private SecurityPolicy securityPolicy; //重写安全校验类型

    public void setSecurityPolicy(SecurityPolicy sp) {
        this.securityPolicy = sp;
    }

    public SecurityPolicy getSecurityPolicy() {
        return this.securityPolicy;
    }

    private String userName;

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public String getUserName() {
        return this.userName;
    }

    private String password;

    public void setPassword(String password1) {
        this.password = password1;
    }

    public String getPassword() {
        return this.password;
    }

    public IdentityProvider getIdentityProvider() { //重写安全校验方式

        if (securityPolicy != null && !securityPolicy.name().equals("None")) {
            return new UsernameProvider(userName, password);
        } else {
            return new AnonymousProvider();
        }
    }

    private String connectionID;

    public void setConnectionID(String id) {
        this.connectionID = id;
    }

    public String getConnectionID() {
        return this.connectionID;
    }

    private String namespaceIndex;

    public void setNamespaceIndex(String ns) {
        this.namespaceIndex = ns;
    }

    private boolean isRunning;

    public void setRunning(boolean running) {
        this.isRunning = running;
    }

    public boolean getIsRunning() {
        return this.isRunning;
    }

    public String getNamespaceIndex() {
        return this.namespaceIndex;
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private OpcUaClient uaClient;

    private CompletableFuture<OpcUaClient> uaFuture;
    /**
     * 心跳数据
     */
    private int heartBeatSecond;

    public void setHeartBeat(int heartBeart) {
        this.heartBeatSecond = heartBeart;
    }

    public int getHeartBeat() {
        return this.heartBeatSecond;
    }

    //OPC 心跳地址
    private final String OPC_HEART_BEAT = "_System._Time_Second";

    @Override
    public void run(OpcUaClient client, CompletableFuture<OpcUaClient> future,Variant variant) throws Exception {
        // synchronous connect
        client.connect().get();
        this.isRunning = true;
        uaClient = client;
        uaFuture = future;
        OpcConnection _this = this;
//         variant = new Variant("777");

        subscript();//TODO 订阅所有需要订阅的节点
        bindWriteNode();// TODO 绑定所有下发的点
       writeNodeValue("test111.666.test6",variant);

        // TODO 轮循方式读取节点数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        List<SubscribeNode> lst = SubscribeNodeFactory.This().getSubscribeNodesByServer(connectionID, INodeType.OPC_READ);
                        for (SubscribeNode sn : lst) {
                            if (sn.getOpcConnection() == null) {
                                sn.setOpcConnection(_this);
                            }

                            if (sn.getReadType() == 1) {
                                readNodes(sn);
                            }
                        }
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        logger.error("Error :", e.getMessage(), e);
                    }
                }
            }
        }).start();
    }

    /**
     * 绑定所有下发的点
     */
    public void bindWriteNode() {
        List<SubscribeNode> lst = SubscribeNodeFactory.This().getSubscribeNodesByServer(connectionID, INodeType.OPC_WRITE);
        for (SubscribeNode sn : lst) {
            if (sn.getOpcConnection() == null) {
                sn.setOpcConnection(this);
            }
        }
    }

    /**
     * 订阅所有需要订阅的节点
     **/
    public void subscript() throws ExecutionException, InterruptedException {
        List<UaSubscription> subscriptions = uaClient.getSubscriptionManager().getSubscriptions();
        UaSubscription subscription;
        if (subscriptions == null || subscriptions.size() == 0) {
            //创建发布间隔1000ms的订阅对象
            subscription = uaClient.getSubscriptionManager().createSubscription(1000.0).get();
        } else {
            subscription = subscriptions.get(0);
        }
        UaSubscription finalSubscription = subscription;
        List<SubscribeNode> nodes = SubscribeNodeFactory.This().getSubscribeNodesByServer(connectionID, INodeType.OPC_SUBSCRIBE);
        SubscribeNode snHeartBeat = new SubscribeNode();//心跳信息
        snHeartBeat.setServerID(this.connectionID);
        snHeartBeat.setNodeID(0);
        snHeartBeat.setNodePath(this.OPC_HEART_BEAT);
        snHeartBeat.setReadType(0);
        nodes.add(snHeartBeat);
        nodes.forEach((v) -> {
            if (!v.isSubscribe() && v.getReadType() == 0) {
                ////创建订阅的变量
                ReadValueId readValueId = new ReadValueId(
                        NodeId.parse("ns="
                                .concat(this.namespaceIndex)
                                .concat(";s=")
                                .concat(v.getNodePath())),
                        AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);

                // important: client handle must be unique per item
                //UInteger clientHandle = uint(clientHandles.getAndIncrement());
                UInteger clientHandle = uint(v.getNodeID());
                //创建监控的参数
                MonitoringParameters parameters = new MonitoringParameters(
                        clientHandle,
                        1000.0,     // sampling interval
                        null,                 // filter, null means use default
                        uint(10),            // queue size
                        true          // discard oldest
                );
                //创建监控项请求
                //该请求最后用于创建订阅。
                MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
                        readValueId, MonitoringMode.Reporting, parameters);

                BiConsumer<UaMonitoredItem, Integer> onItemCreated =
                        (item, id) -> item.setValueConsumer(this::onSubscriptionValue);
                List<UaMonitoredItem> items = null;
                try {
                    items = finalSubscription.createMonitoredItems(TimestampsToReturn.Both,
                            newArrayList(request),
                            onItemCreated
                    ).get();

                    // TODO 订阅成功后，设置订阅的序号
                    v.setSubscriptID(clientHandle.intValue());//订阅的编号，只有订阅成功后才会生成
                    v.setSubscribe(true);//订阅状态，代表是否订阅，true是已经订阅，false是未订阅
                    v.setMonitoredItem(items.get(0));//订阅的节点
                    v.setOpcConnection(this);//当前节点的OPC连接

                } catch (InterruptedException | ExecutionException e) {
                    logger.error("Error", e.getMessage(), e);
                    Thread.currentThread().interrupt();
                }
                assert items != null;
                for (UaMonitoredItem item : items) {
                    if (item.getStatusCode().isGood()) {
                        logger.info("item created for nodeId={}", item.getReadValueId().getNodeId());
                    } else {
                        logger.error(
                                "failed to create item for nodeId={} (status={})",
                                item.getReadValueId().getNodeId(), item.getStatusCode());
                    }
                }
            }
        });
    }

    /**
     * 订阅监控节点的回调函数
     */
    private void onSubscriptionValue(UaMonitoredItem item, DataValue value) {
        logger.info("subscription value received:ServerID={} item={}, value={}, cid={}",
                this.getConnectionID(), item.getReadValueId().getNodeId(), value.getValue(), item.getClientHandle());
        if (!item.getStatusCode().isGood() && !isRunning) {
            return;
        }
        try {
            if (item.getClientHandle().intValue() != 0) {
                SubscribeNode sn = SubscribeNodeFactory.This().getSubscriptNode(item.getClientHandle().intValue(), INodeType.OPC_SUBSCRIBE);//获取订阅的节点
                sn.setNodeValue(value.getValue());//订阅成功，把订阅成功的值赋给该节点
                sn.publishData();//发布数据
                try {
                    if (sn != null && sn.getGroupName().equals("shift") || sn.getGroupName().equals("equipmentAbnormal") || sn != null && sn.getGroupName().equals("controlSignal") || sn != null && sn.getGroupName().equals("monthlyOutput") || sn != null && sn.getGroupName().equals("eps")) {
                        DeviceLogFactory.This().doLogHandle(sn);
                    }
                } catch (Exception e) {
                    logger.error("doLogHandle error = {} ", e.getMessage());
                }
            } else {
                //重置心跳计数
                this.heartBeatSecond = 0;
            }
        } catch (Exception e) {
            logger.error("subscribe reading error = {} ", e.getMessage());
        }
    }

    /**
     *  读取目录节点信息(暂不使用)
     * @param indent 当前节点名称
     * @param client UA连接
     * @param browseRoot 当前节点
     * */
    /*private String browseNode(String indent, OpcUaClient client, NodeId browseRoot) {
        String nodeName="";
        try {
            NodeId.parse("path");
            List<Node> childNodes = client.getAddressSpace().browse(browseRoot).get();

            for (Node rd : childNodes) {
                String     iIntent = "";
                nodeName = rd.getBrowseName().get().getName();

                if (nodeName.indexOf("_") == 0 ||
                        nodeName.equalsIgnoreCase("Types") ||
                        nodeName.equalsIgnoreCase("Views") ||
                        nodeName.equalsIgnoreCase("Server")) {
                    //TODO:过滤不会使用到的节点
                    continue;
                }

                if (indent.isEmpty()) {
                    if(!nodeName.equalsIgnoreCase("Objects"))
                        iIntent = nodeName;
                }
                else {
                    iIntent = (indent + "." + nodeName);
                }

                //TODO:读取子目录
                String childName = browseNode(iIntent, client,rd.getNodeId().get());
                if(childName.isEmpty()) {
                    int    ns = rd.getBrowseName().get().getNamespaceIndex().intValue();
                    String path = String.format("ns=%d;s=%s", ns, indent + "." + nodeName);
                    SubscribeNode sn = new SubscribeNode();
                    sn.setNodePath(path);
                    sn.setOpcServerID(this.connectionID);
                    sn.setSubscribe(false);
                    // this.nodes.add(sn);
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            logger.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
            Thread.currentThread().interrupt();
        }
        return nodeName;
    }*/

    /**
     * 轮循读取节点数据
     **/
    private void readNodes(SubscribeNode sn) {
        try {
            VariableNode node = this.uaClient.getAddressSpace().createVariableNode(NodeId.parse("ns="
                    .concat(this.namespaceIndex)
                    .concat(";s=")
                    .concat(sn.getNodePath())));
            //CompletableFuture<DataValue> s = node.readValue();
            DataValue value = node.readValue().get();
            if (value != null && value.getValue() != null) {
                sn.setNodeValue(value.getValue());
            }
            sn.publishData();//发布数据
            logger.info("read ServerID ={}  node = {} value={}", this.getConnectionID(), sn.getNodePath(), sn.getNodeValue().getValue());
        } catch (Exception e) {
            logger.error("Reading node Error = {}", e.getMessage());
        }
        //日志处理
        DeviceLogFactory.This().doLogHandle(sn);
        try {
            //SubscribeNodeFactory.This().saveNodes2Redis(sn);//写缓存
        } catch (Exception e) {
            logger.error("saveNodes2Redis error = {} ", e.getMessage());
        }
    }

    /**
     * 往OPC写值
     * nodePath:OPC节点路径
     * val: 要写入
     **/

    // KKK
    public boolean writeNodeValue(String nodePath, Variant val) {
        DataValue dv = new DataValue(val, null, null);
        boolean flag = false;
        try {
            StatusCode statusCode = this.uaClient.writeValue(NodeId.parse("ns="
                    .concat(this.namespaceIndex)
                    .concat(";s=")
                    .concat(nodePath)), dv).get();
            if (statusCode.isGood()) {
                flag = true;
            }
        } catch (InterruptedException e) {
            logger.error("Writing node Error = {}", e.getMessage());
        } catch (ExecutionException e) {
            logger.error("Writing node Error = {}", e.getMessage());
        }
        return flag;
    }

    /**
     * 根据路径读取OPC的值
     **/
    public Variant readNodeValue(String nodePath) {
        Variant v = null;
        try {
            VariableNode node = this.uaClient.getAddressSpace().createVariableNode(NodeId.parse("ns="
                    .concat(this.namespaceIndex)
                    .concat(";s=")
                    .concat(nodePath)));
            DataValue value = node.readValue().get();
            if (value != null) {
                v = value.getValue();
            }

        } catch (InterruptedException e) {
            logger.error("Read node Error = {}", e.getMessage());
        } catch (ExecutionException e) {
            logger.error("Read node Error = {}", e.getMessage());
        }
        return v;
    }

    /**
     * 关闭当前连接
     */
    public void close() {
        this.isRunning = false;

        if (uaFuture != null) {
            uaFuture.complete(uaClient);
        }

    }
}
